﻿namespace PugLib.Collections
{
	using System;
	using System.Collections.Generic;
	using System.ComponentModel;

	public class SortableSearchableCollection<T> : BindingList<T>
	{
		private ListSortDirection _sortDirectionValue;
		private List<object> _sortedList;
		private PropertyDescriptor _sortPropertyValue;
		private List<object> _unsortedItems;

		protected override PropertyDescriptor SortPropertyCore
		{
			get { return _sortPropertyValue; }
		}

		protected override ListSortDirection SortDirectionCore
		{
			get { return _sortDirectionValue; }
		}

		protected override bool SupportsSortingCore
		{
			get { return true; }
		}

		protected override bool SupportsSearchingCore
		{
			get { return true; }
		}

		public override void EndNew(int itemIndex)
		{
			if (_sortPropertyValue != null && itemIndex > 0
			    && itemIndex == Count - 1)
			{
				ApplySortCore(_sortPropertyValue, _sortDirectionValue);
			}
			base.EndNew(itemIndex);
		}

		public void RemoveSort()
		{
			RemoveSortCore();
		}

		protected override void ApplySortCore(PropertyDescriptor prop, ListSortDirection direction)
		{
			_sortedList = new List<object>();

			// Check to see if the property type we are sorting by implements
			// the IComparable interface.
			Type interfaceType = prop.PropertyType.GetInterface("IComparable");

			if (interfaceType == null)
			{
				// not sortable b/c it doesn't implement IComparable
				return;
			}

			// If so, set the SortPropertyValue and SortDirectionValue.
			_sortPropertyValue = prop;
			_sortDirectionValue = direction;

			_unsortedItems = new List<object>(Count);

			// Loop through each item, adding it the the sortedItems ArrayList.
			for (int i = 0; i < Items.Count; i++)
			{
				T item = Items[i];
				_sortedList.Add(prop.GetValue(item));
				_unsortedItems.Add(item);
			}

			_sortedList.Sort();

			// Check the sort direction and then copy the sorted items
			// back into the list.
			if (direction == ListSortDirection.Descending)
			{
				_sortedList.Reverse();
			}

			for (int i = 0; i < Count; i++)
			{
				int position = Find(prop.Name, _sortedList[i]);
				if (position != i &&
				    position > -1)
				{
					T temp = this[i];
					this[i] = this[position];
					this[position] = temp;
				}
			}
			// Raise the ListChanged event so bound controls refresh their
			// values.
			OnListChanged(new ListChangedEventArgs(ListChangedType.Reset, -1));
		}

		protected override void RemoveSortCore()
		{
			// Ensure the list has been sorted.
			if (_unsortedItems != null)
			{
				// Loop through the unsorted items and reorder the
				// list per the unsorted list.
				for (int i = 0; i < _unsortedItems.Count;)
				{
					int position = Find(SortPropertyCore.Name,
					                    _unsortedItems[i].GetType().
					                    	GetProperty(SortPropertyCore.Name).
					                    	GetValue(_unsortedItems[i], null));
					if (position >= 0 && position != i)
					{
						object temp = this[i];
						this[i] = this[position];
						this[position] = (T)temp;
						i++;
					}
					else if (position == i)
					{
						i++;
					}
					else
					{
						// If an item in the unsorted list no longer exists, delete it.
						_unsortedItems.RemoveAt(i);
					}
				}
				OnListChanged(new ListChangedEventArgs(ListChangedType.Reset, -1));
			}
		}

		protected override int FindCore(PropertyDescriptor prop, object key)
		{
			// Loop through the the items to see if the key
			// value matches the property value.
			for (int i = 0; i < Count; ++i)
			{
				if (Reflect.Get(prop.Name, Items[i]) == key)
				{
					return i;
				}
			}
			return -1;
		}

		public int Find(string property, object key)
		{
			// Check the properties for a property with the specified name.
			PropertyDescriptorCollection properties = TypeDescriptor.GetProperties(typeof(T));
			PropertyDescriptor prop = properties.Find(property, true);

			// If there is not a match, return -1 otherwise pass search to
			// FindCore method.
			if (prop == null)
			{
				return -1;
			}
			return FindCore(prop, key);
		}
	}
}